unit Success;
{
  Class that parses the invention formula, and provides invention probability based on 8 parameters:
  1) Base chance
  2) Encryption skill level
  3) skill 1 level
  4) skill 2 level
  5) Datacore 1 amount
  6) Datacore 2 amount
  7) Decryptor chance modifier
  8) Meta-item level
}

interface

uses
  GlobalRecordsAndEnums, Formula, Classes;

type
  TSuccess = class
    private
      _formula: String[255];
      _useformula: Boolean;
      _stillusedecryptor: Boolean;
      _basenormalchance: Double;
      _baseshipchance: Double;
      _baserigchance: Double;

      FormulaEntry: TOper;
      Tokens: TList;
      CanUseFormula: Boolean;

      _base: Double;
      _encrskill: Double;
      _skill1: Double;
      _skill2: Double;
      _datacore1: Double;
      _datacore2: Double;
      _decryptor: Double;
      _meta: Double;

      function GetFormula: String;
      function GetIdentifier(s: String): EIdentifier;
      function GetOperator(c: Char): EOperator;
      function CalculateOperation(op: TOper): Double;
    public
      constructor Create;
      procedure Load(success: SSuccess);
      destructor Destroy; reintroduce;
      function Save: SSuccess;
      function GetSuccess(Inventiontype: EInventionType; BaseChance: Double; EncrSkillLevel, Skill1Level, Skill2Level, Datacore1Amount, Datacore2Amount: Integer; DecryptorModifier: Double; MetaLevel: Double): Double;
      function SetFormula(formula: String; Test: Boolean = false): String; // returns location of error as string. Empty string if no error occured

      property Formula: String read GetFormula;
      property UseFormula: Boolean read _useformula write _useformula;
      property StillUseDecryptor: Boolean read _stillusedecryptor write _stillusedecryptor;
      property BaseNormalChance: Double read _basenormalchance write _basenormalchance;
      property BaseShipChance: Double read _baseshipchance write _baseshipchance;
      property BaseRigChance: Double read _baserigchance write _baserigchance;
  end;

implementation

uses
  SysUtils, Contnrs, Math, Dialogs;

constructor TSuccess.Create;
var
  formulaerror: String;
begin
  Inherited Create;
  Tokens := TList.Create;
  formulaerror := self.SetFormula('%Base% * (1+(0'+global_ds+'01*%EncrSkill%)) * (1+((%Skill1% + %Skill2%) * (0'+global_ds+'1 / (5 - %Meta%)))) * %Decryptor%');
//  formulaerror := self.SetFormula('%Base% * (1+(0'+global_ds+'01*%EncrSkill%)) * (1+(0'+global_ds+'005*%Skill1%)) * (1+(0'+global_ds+'005*%Skill2%)) * %Decryptor% * (1+(0'+global_ds+'1 * %Meta%))');
  if (formulaerror <> '') then begin
    MessageDlg(formulaerror + #10#13#10#13 + 'The invention formula was reset to the default formula.', mtError, [mbOk], 0);
  end;
  _useformula := true;
  _stillusedecryptor := false;
  _basenormalchance := 0.4;
  _baseshipchance := 0.2;
  _baserigchance := 0.3;
end;

procedure TSuccess.Load(success: SSuccess);
var
  formulaerror: String;
begin
  formulaerror := self.SetFormula(success.Formula);
  if (formulaerror <> '') then begin
    MessageDlg(formulaerror + #10#13#10#13 + 'The invention formula was reset to the default formula.', mtError, [mbOk], 0);
  end;
  _useformula := success.UseFormula;
  _stillusedecryptor := success.StillUseDecryptor;
  _basenormalchance := success.BaseNormalChance;
  _baseshipchance := success.BaseShipChance;
  _baserigchance := success.BaseRigChance;
end;

destructor TSuccess.Destroy;
begin
  if (self.FormulaEntry <> nil) then FormulaEntry.Destroy;
  Tokens.Destroy;
  Inherited Destroy;
end;

function TSuccess.Save: SSuccess;
begin
  result.Formula := _formula;
  result.UseFormula := _useformula;
  result.StillUseDecryptor := _stillusedecryptor;
  result.BaseNormalChance := _basenormalchance;
  result.BaseShipChance := _baseshipchance;
  result.BaseRigChance := _baserigchance;
end;

function TSuccess.GetFormula: String;
begin
  result := String(_formula);
end;

function TSuccess.GetSuccess(InventionType: EInventionType; BaseChance: Double; EncrSkillLevel, Skill1Level, Skill2Level, Datacore1Amount, Datacore2Amount: Integer; DecryptorModifier: Double; MetaLevel: Double): Double;
begin
  if (not CanUseFormula) then result := 0
  else begin
    if (_useformula) then begin
      _base := BaseChance;
      _encrskill := EncrSkillLevel;
      _skill1 := Skill1Level;
      _skill2 := Skill2Level;
      _datacore1 := Datacore1Amount;
      _datacore2 := Datacore2Amount;
      _decryptor := DecryptorModifier;
      _Meta := MetaLevel;

      result := self.CalculateOperation(FormulaEntry);
    end
    else begin
      case InventionType of
        Normal: result := _basenormalchance;
        Ship: result := _baseshipchance;
        Rig: result := _baserigchance;
      else result := 0;
      end;
      if (_stillusedecryptor) then result := result * DecryptorModifier;
    end;
  end;
  result := Min(result, 1);
end;

function TSuccess.GetIdentifier(s: String): EIdentifier;
begin
  if (Uppercase(s) = 'BASE') then result := Base
  else if (Uppercase(s) = 'ENCRSKILL') then result := EncrSkill
  else if (Uppercase(s) = 'SKILL1') then result := Skill1
  else if (Uppercase(s) = 'SKILL2') then result := Skill2
  else if (Uppercase(s) = 'DATACORE1') then result := Datacore1
  else if (Uppercase(s) = 'DATACORE2') then result := Datacore2
  else if (Uppercase(s) = 'DECRYPTOR') then result := Decryptor
  else if (Uppercase(s) = 'META') then result := Meta
  else result := Error;
end;

function TSuccess.GetOperator(c: Char): EOperator;
begin
  if (c = '+') then result := Add
  else if (c = '-') then result := Subtract
  else if (c = '*') then result := Multiply
  else if (c = '/') then result := Divide
  else if (c = '^') then result := Pwr
end;

function TSuccess.SetFormula(formula: String; Test: Boolean = false): String;
var
  oldformula: String;
  f: String;
  currenttoken: ETokenType;
  currentoperation: TOper;
  id: String;
  i, par: Integer;

  op: TOper;
  con: Double;
begin
  oldformula := self._formula;
  self._formula := formula;

  Tokens.Clear;
  f := formula;
  currenttoken := BeginOperation;
  par := 0;
  for i := 1 to Length(f) do begin
    case f[i] of
      ' ', #13, #10: begin { skip } end;
      '%': begin
        case currenttoken of
          Constant: result := 'Error at ' + IntToStr(i) + ': Missing operator.';
          Operator: begin
                      currenttoken := BeginIdentifier;
                      id := '';
                    end;
          BeginIdentifier: begin
                             currenttoken := EndIdentifier;
                             if (GetIdentifier(id) <> Error) then
                               Tokens.Add(TIdentifier.Create(GetIdentifier(id)))
                             else
                               result := 'Error at ' + IntToStr(i) + ': "%' + id + '%" is an unknown identifier.';
                           end;
          EndIdentifier: result := 'Error at ' + IntToStr(i) + ': Missing operator.';
          BeginOperation: begin
                            currenttoken := BeginIdentifier;
                            id := '';
                          end;
          EndOperation: result := 'Error at ' + IntToStr(i) + ': Missing operator.';
        end;
      end;

      '+', '-', '*', '/', '^': begin
        case currenttoken of
          Constant: begin
                      if (TryStrToFloat(id, con)) then begin
                        Tokens.Add(TConstant.Create(con));
                        currenttoken := Operator;
                        Tokens.Add(TOperator.Create(GetOperator(f[i])));
                      end
                      else result := 'Error at ' + IntToStr(i) + '. "' + id + '" is not a valid constant.';
                    end;
          Operator: result := 'Error at ' + IntToStr(i) + ': Parameter, opening parenthesis or constant expected, but operator found.';
          BeginIdentifier: result := 'Error at ' + IntToStr(i) + ': Parameter expected, but operator found.';
          EndIdentifier: begin
                           currenttoken := Operator;
                           Tokens.Add(TOperator.Create(GetOperator(f[i])));
                         end;
          BeginOperation: result := 'Error at ' + IntToStr(i) + ': Parameter, opening parenthesis or constant expected, but operator found.';
          EndOperation: begin
                          currenttoken := Operator;
                          Tokens.Add(TOperator.Create(GetOperator(f[i])));
                        end;

        end;
      end;
      '0', '3', '4', '5', '6', '7', '8', '9', ',', '.': begin
        case currenttoken of
          Constant: begin id := id + f[i]; end;
          Operator: begin
                      currenttoken := Constant;
                      id := f[i];
                    end;
          BeginIdentifier: result := 'Error at ' + IntToStr(i) + ': Parameter expected, but constant found.';
          EndIdentifier: result := 'Error at ' + IntToStr(i) + ': Missing operator.';
          BeginOperation: begin
                            currenttoken := Constant;
                            id := f[i];
                          end;
          EndOperation: result := 'Error at ' + IntToStr(i) + ': Missing operator.';
        end;
      end;
      '1', '2': begin
        case currenttoken of
          Constant: begin id := id + f[i]; end;
          Operator: begin
                      currenttoken := Constant;
                      id := f[i];
                    end;
          BeginIdentifier: begin id := id + f[i]; end;
          EndIdentifier: result := 'Error at ' + IntToStr(i) + ': Missing operator.';
          BeginOperation: begin
                            currenttoken := Constant;
                            id := f[i];
                          end;
          EndOperation: result := 'Error at ' + IntToStr(i) + ': Missing operator.';
        end;
      end;
      '(': begin
        case currenttoken of
          Constant: result := 'Error at ' + IntToStr(i) + ': Missing operator.';
          Operator:  begin
                       Tokens.Add(TBeginOper.Create);
                       Inc(par);
                     end;
          BeginIdentifier: result := 'Error at ' + IntToStr(i) + ': Parameter expected, but parenthesis found.';
          EndIdentifier: result := 'Error at ' + IntToStr(i) + ': Missing operator.';
          BeginOperation: begin
                            Tokens.Add(TBeginOper.Create);
                            Inc(par);
                          end;
          EndOperation: result := 'Error at ' + IntToStr(i) + ': Missing operator.';
        end;
      end;
      ')': begin
        case currenttoken of
          Constant: begin
                      if (TryStrToFloat(id, con)) then begin
                        Tokens.Add(TConstant.Create(con));
                        Tokens.Add(TEndOper.Create);
                        currenttoken := EndOperation;
                        Dec(par);
                      end
                      else result := 'Error at ' + IntToStr(i) + '. "' + id + '" is not a valid constant.';
                    end;
          Operator: result := 'Error at ' + IntToStr(i) + ': Parameter, opening parenthesis or constant expected, but closing parenthesis found.';
          BeginIdentifier: result := 'Error at ' + IntToStr(i) + ': Parameter expected, but parenthesis found.';
          EndIdentifier: begin
                           Tokens.Add(TEndOper.Create);
                           currenttoken := EndOperation;
                           Dec(par);
                         end;
          BeginOperation: begin
                            Tokens.Add(TEndOper.Create);
                            currenttoken := EndOperation;
                            Dec(par);
                          end;
          EndOperation: begin
                          Tokens.Add(TEndOper.Create);
                          currenttoken := EndOperation;
                          Dec(par);
                        end;
        end;
      end;
      'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 't', 's', 'u', 'v', 'w', 'x', 'y', 'z',
      'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z': begin
        case currenttoken of
          Constant: result := 'Error at ' + IntToStr(i) + ': Constant or operator expected.';
          Operator:  result := 'Error at ' + IntToStr(i) + ': Constant, opening parenthesis or parameter expected.';
          BeginIdentifier: begin id := id + f[i]; end;
          EndIdentifier:  result := 'Error at ' + IntToStr(i) + ': Operator expected.';
          BeginOperation:  result := 'Error at ' + IntToStr(i) + ': Constant, opening parenthesis or parameter expected.';
          EndOperation:  result := 'Error at ' + IntToStr(i) + ': Operator expected.';
        end;
      end;
      else result := 'Error at ' + IntToStr(i) + '. Invalid character.';
    end;
    if (Result <> '') then break;
  end; { For }
  if (Length(f) < 1) then result := 'Error: No formula!';
  if (result = '') then begin
    if (currenttoken = Constant) then begin
      if (TryStrToFloat(id, con)) then begin
        Tokens.Add(TConstant.Create(con));
      end
      else result := 'Error at ' + IntToStr(i) + '. "' + id + '" is not a valid constant.';
    end
    else if (currenttoken = BeginIdentifier) then begin
      result := 'Error at ' + IntToStr(i) + ': Parameter expected, but end found.';
    end;
  end;

  if (result = '') then begin
    if (par < 0) then result := 'Error: Too many closing parenthesis'
    else if (par > 0) then result := 'Error: Too many opening parenthesis';
  end;

  if (result <> '') then self._formula := oldformula;

  // check token list
{  if (result = '') then begin
    for i := 0 to Tokens.Count -1 do begin
      if (TToken(Tokens[i]).ClassName = 'TOperator') then begin
        case TOperator(Tokens[i]).Operator of
          Add: result := result + ' + ';
          Subtract: result := result + ' - ';
          Multiply: result := result + ' * ';
          Divide: result := result + ' / ';
          Power: result := result + ' ^ ';
        end;
      end
      else if (TToken(Tokens[i]).ClassName = 'TConstant') then begin
        result := result + FloatToStr(TConstant(Tokens[i]).Value);
      end
      else if (TToken(Tokens[i]).ClassName = 'TBeginOper') then begin
        result := result + '(';
      end
      else if (TToken(Tokens[i]).ClassName = 'TEndOper') then begin
        result := result + ')';
      end
      else if (TToken(Tokens[i]).ClassName = 'TIdentifier') then begin
        case TIdentifier(Tokens[i]).Value of
          Base: result := result + 'Base';
          EncrSkill: result := result + 'EncrSkill';
          Skill1: result := result + 'Skill1';
          Skill2: result := result + 'Skill2';
          Datacore1: result := result + 'Datacore1';
          Datacore2: result := result + 'Datacore2';
          Decryptor: result := result + 'Decryptor';
          Meta: result := result + 'Meta';
          Implants: result := result + 'Implants';
        end;
      end
      else result := result + TObject(Tokens[i]).ClassName;
    end;
  end;
}

  // convert token list to operations
  if (result = '') then begin
    if (FormulaEntry <> nil) then FormulaEntry.Destroy;
    currentoperation := TOper.Create(nil);
    FormulaEntry := currentoperation;

    for i := 0 to Tokens.Count -1 do begin
      if (TToken(Tokens[i]).ClassName = 'TOperator') then begin
        currentoperation.AddOperator(TOperator(Tokens[i]));
      end
      else if (TToken(Tokens[i]).ClassName = 'TConstant') then begin
        currentoperation.AddToken(TToken(Tokens[i]));
      end
      else if (TToken(Tokens[i]).ClassName = 'TBeginOper') then begin
        op := TOper.Create(currentoperation);
        currentoperation.AddToken(op);
        currentoperation := op;
      end
      else if (TToken(Tokens[i]).ClassName = 'TEndOper') then begin
        currentoperation := currentoperation.Parent;
      end
      else if (TToken(Tokens[i]).ClassName = 'TIdentifier') then begin
        currentoperation.AddToken(TIdentifier(Tokens[i]));
      end
    end;

    // test formula
    if (Test) then begin
      _base := 0.40;
      _encrSkill := 4;
      _skill1 := 4;
      _skill2 := 4;
      _datacore1 := 2;
      _datacore2 := 2;
      _decryptor := 1.0;
      _meta := 1;

      result := 'Testing formula with the following parameters: ' + #10#13 +
      'Base: 0.40' + #10#13 +
      'EncrSkill: 4' + #10#13 +
      'Skill1: 4' + #10#13 +
      'Skill2: 4' + #10#13 +
      'Datacore1: 2' + #10#13 +
      'Datacore2: 2' + #10#13 +
      'Decryptor: 1.0' + #10#13 +
      'Meta: 1' + #10#13 +
      'Chance for success: ' + FloatToStrF(Min(CalculateOperation(FormulaEntry), 1), ffNumber, 5, 4);
    end;
  end;
  CanUseFormula := (result = '');
end;

function TSuccess.CalculateOperation(op: TOper): Double;
var
  i: Integer;
//  _Base, _EncrSkill, _Skill1, _Skill2, _Datacore1, _Datacore2, _Decryptor, _Meta, _Implants: Double;
  token: TToken;
  constant, current: Double;
begin
  current := 0;
  token := op.GetToken(0);
  if (token.ClassName = 'TConstant') then begin
    current := TConstant(token).Value;
  end
  else if (token.ClassName = 'TIdentifier') then begin
    case TIdentifier(token).Value of
      Base: current := _Base;
      EncrSkill: current := _EncrSkill;
      Skill1: current := _Skill1;
      Skill2: current := _Skill2;
      Datacore1: current := _Datacore1;
      Datacore2: current := _Datacore2;
      Decryptor: current := _Decryptor;
      Meta: current := _Meta;
    end;
  end
  else if (token.ClassName = 'TOper') then begin
      current := CalculateOperation(TOper(token));
  end
  else current := 0;



  for i := 1 to op.TokenCount -1 do begin
    token := op.GetToken(i);
    if (token.ClassName = 'TConstant') then begin
      constant := TConstant(token).Value;
    end
    else if (token.ClassName = 'TIdentifier') then begin
      case TIdentifier(token).Value of
        Base: constant := _Base;
        EncrSkill: constant := _EncrSkill;
        Skill1: constant := _Skill1;
        Skill2: constant := _Skill2;
        Datacore1: constant := _Datacore1;
        Datacore2: constant := _Datacore2;
        Decryptor: constant := _Decryptor;
        Meta: constant := _Meta;
      end;
    end
    else if (token.ClassName = 'TOper') then begin
      constant := CalculateOperation(TOper(token));
    end
    else constant := 1;


    case op.GetOperator(i-1).Operator of
      Add: current := current + constant;
      Subtract: current := current - constant;
      Multiply: current := RoundTo(current * constant, -4);
      Divide: begin
                if (constant <> 0) then current := current / constant
                else current := 0;
              end;
      Pwr: current := Math.Power(current, constant);
    end;
  end; // for
  result := current;
end;

end.
